package com.lizemin.bank.filter;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.lizemin.bank.common.RespBean;
import com.lizemin.bank.constants.Constants;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.IntrospectionUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.SecureRandom;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;

/**
 * @author lzm
 * @date 2025/4/12 10:44
 * @description
 */
@Slf4j
@Data
public class CsrfFilter implements Filter {

    /**
     * 是否开启csrf校验
     */
    private Boolean enabled = Boolean.TRUE;

    /**
     * 白名单，不需要csrf校验的接口，用逗号分隔
     */
    private String endPoint;

    /**
     * 登录的url
     */
    private String loginUrl;

    private SecureRandom randomSource = new SecureRandom();

    public void init(FilterConfig filterConfig) {
        Enumeration<String> paramNames = filterConfig.getInitParameterNames();
        while (paramNames.hasMoreElements()) {
            String paramName = paramNames.nextElement();
            String paramValue = filterConfig.getInitParameter(paramName);
            IntrospectionUtils.setProperty(this, paramName, paramValue);
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (!(request instanceof HttpServletRequest)) {
            chain.doFilter(request, response);
            return;
        }
        if (!(response instanceof HttpServletResponse)) {
            chain.doFilter(request, response);
            return;
        }
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        if (!enabled) {
            log.info("未开启csrf校验，直接放行");
            chain.doFilter(request, response);
            return;
        }
        log.info("开始csrf校验");
        String uri = req.getRequestURI();
        if (StrUtil.isNotBlank(endPoint)) {
            List<String> endPoints = StrUtil.split(endPoint, ",");
            for (String point : endPoints) {
                if (uri.endsWith(point)) {
                    log.info("{}不需要csrf校验，直接放行", uri);
                    chain.doFilter(request, response);
                    return;
                }
            }
        }
        if (uri.endsWith(loginUrl)) {
            // 登录接口直接放行
            HttpSession session = req.getSession();
            String token = generateNonce();
            session.setAttribute(Constants.CSRF_SESSION_ATTRIBUTE, token);
            resp.setHeader(Constants.CSRF_HEADER_NAME, token);
            // 设置响应头需要在写响应体之前，也就是调用接口前设置响应头，一旦写入了响应体，就不能再设置响应头了
            // 所以一定要在放行请求前设置响应头
            chain.doFilter(req, resp);
            return;
        }
        HttpSession session = req.getSession(false);
        String reqToken = req.getHeader(Constants.CSRF_HEADER_NAME);
        if (StrUtil.isBlank(reqToken) || Objects.isNull(session)) {
            log.info("请求中没有csrf token或者session为空");
            buildFailResponse(resp);
            return;
        }

        String sessionToken = (String) session.getAttribute(Constants.CSRF_SESSION_ATTRIBUTE);
        if (!StrUtil.equals(sessionToken, reqToken)) {
            buildFailResponse(resp);
            return;
        }
        log.info("csrf校验通过");
        chain.doFilter(req, resp);
    }

    private static void buildFailResponse(HttpServletResponse resp) throws IOException {
        resp.setContentType("application/json;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        RespBean responseBody = RespBean.fail("跨站请求伪造！");
        writer.write(JSONUtil.toJsonStr(responseBody));
        writer.flush();
        writer.close();
    }

    /**
     * 生成随机数
     *
     * @return 随机数
     */
    private String generateNonce() {
        byte[] random = new byte[16];
        StringBuilder buffer = new StringBuilder();
        randomSource.nextBytes(random);
        for (byte b : random) {
            byte b1 = (byte) ((b & 0xf0) >> 4);
            byte b2 = (byte) (b & 0x0f);
            if (b1 < 10) {
                buffer.append((char) ('0' + b1));
            } else {
                buffer.append((char) ('A' + (b1 - 10)));
            }
            if (b2 < 10) {
                buffer.append((char) ('0' + b2));
            } else {
                buffer.append((char) ('A' + (b2 - 10)));
            }
        }
        return buffer.toString();
    }


}
